/**
 * Esta classe é uma especialização da classe
 * <b>Hashtable</b>, aplicada às Strings.<br>
 * Adaptado para as aulas de IHC, Jan 2008.
 * <br>
 * João P. C. Cordeiro, Agosto 2002
 */
import java.io.*;
import java.util.*;


public class HashStr extends Hashtable<String, Integer> implements Serializable
{
    private int M= 0;


    /**
     * Construtor 1
     */
    public HashStr()
    {
        super();
    }


    /**
     * Só conta/acrescenta palavras - ignora outro
     * tipo de strings.
     */
    public void countWord(String word) {
        int a=0;
        for (; a<word.length(); a++)
            if ( Character.isLetter(word.charAt(a)) )  break;

        int b= word.length()-1;
        for (; a<=b; b--)
            if ( Character.isLetter(word.charAt(b)) )  break;

        if ( a <= b )
            this.increment(word.substring(a, b+1), 1);
    }



    /**
     * Adiciona ou incrementa o valor inteiro correspondente
     * à String enviada <b>skey</b>, de <b>kink</b> unidades.
     */
    public void increment(String skey, int kinc)
    {
        if ( ! this.containsKey(skey) )
            this.put(new String(skey), new Integer(kinc));
        else  {
            Integer I= (Integer)this.get(skey);
            int ik= I.intValue() + kinc;
            this.put(skey,new Integer(ik));
        }

        M+= kinc;
    }


    /**
     * Realiza o método <b>add</b> para
     * um vector de strings.
     */
    public void increment(String[] vs, int kinc)
    {
        if ( vs == null )  return;

        for (int i=0; i<vs.length; i++)  increment(vs[i], kinc);
    }


    /**
     * Numero de strings incrementadas, na
     * estrutura, por exemplo: o numero
     * total de palavras.
     */
    public int num()
    {
        return M;
    }


    /**
     * Frequência da string «key», na
     * estrutura.
     */
    public int freq(String key)
    {
        if ( key == null )  return -1;

        Integer I= (Integer)this.get(key);
        return I == null ? 0 : I.intValue();
    }


    /**
     * Probabilidade estimada da string "key",
     * na estrutura.
     */
    public double prob(String key)
    {
        double p= (double) freq(key) / num();
        return p;
    }


    /**
     * Entropia do conjunto.
     */
    public double entropy()
    {
        return entropy(null);
    }


    /**
     * Entropia do conjunto, excluindo o
     * elemento <b>keyexclude</b>.
     */
    public double entropy(String keyexclude)
    {
        double soma= 0.0, N=0.0;
        for (Enumeration e= keys(); e.hasMoreElements(); )  {
            String skey=(String) e.nextElement();
            if ( keyexclude != null  &&  skey.equals(keyexclude) )  continue;

            Integer Fk=(Integer) get(skey);
            N+= Fk.floatValue();
            soma+= Fk.floatValue() * Math.log(Fk.floatValue());
        }

        return ( 1.0/Math.log(2) * ( Math.log(N) - 1.0/N * soma ) );
    }


    /**
     * Valor de Caos ]0,1]
     */
    public double caos()
    {
        return Math.log(2) * entropy() / Math.log((double)this.size());
    }


    /**
     * Devolve um array de Strings contendo, as
     * chaves da tabela de hash.
     */
    public String[] toArray()
    {
        String[] vs= new String[size()];

        Enumeration e= keys();
        for (int k=0; k<size(); k++)
            vs[k]= (String)e.nextElement();

        return vs;
    }

    /**
     * Invoca o método estático.
     */
    public String[] rankBestN(int N) {
        return rankN(this, N);
    }

    /**
     * Devolve um array com as N melhores Strings/palavras/termos.
     */
    public static String[] rankN(Hashtable<String, Integer> ht, int N)
    {
        if ( ht == null || ht.size() < 1 )  return null;

        Hashtable<String, Integer> h= (Hashtable<String, Integer>) ht.clone();
        String[] vs= new String[Math.min(ht.size(),N)];

        for (int k=0; k<vs.length; k++)  {
            Enumeration e= h.keys();
            String wmx=(String) e.nextElement();
            int max= ht.get(wmx).intValue();

            while ( e.hasMoreElements() )  {
                String w=(String) e.nextElement();
                int emax= ht.get(w).intValue();
                if ( emax > max )  {
                    max= emax;
                    wmx= w;
                }
            }

            vs[k]= new String(wmx);
            h.remove(wmx);
            if ( h.size() < 1 )  break;
        }

        return vs;
    }

    /**
     * Carrega a Hashtable, a partir de uma outra
     * tabela de Hash.
     */
    public void load(Hashtable ht)
    {
        this.clear();

        Enumeration keys= ht.keys();
        while ( keys.hasMoreElements() )  {
            String skey= (String)keys.nextElement();
            Integer I= (Integer)ht.get(skey);
            this.increment(skey,I.intValue());
        }
    }

    /**
     * Carrega a Hashtable, a partir do ficheiro
     * "fname".
     */
    public boolean load(String fname)
    {
        try  {
            FileInputStream istream= new FileInputStream(fname);
            ObjectInputStream ois= new ObjectInputStream(istream);

            HashStr hstr=(HashStr) ois.readObject();
            this.load(hstr);
            ois.close();
        }
        catch (Exception e)  {
            System.out.println("ERROR - "+e);
            return false;
        }

        return true;
    }

    /**
     * Grava a Hashtable num ficheiro com o nome "fname".
     */
    public boolean save(String fname)
    {
        try  {
            FileOutputStream ostream= new FileOutputStream(fname);
            ObjectOutputStream oos= new ObjectOutputStream(ostream);

            oos.writeObject(this);
            oos.flush();
            oos.close();
        }
        catch (Exception e)  {
            System.out.println("ERROR - "+e);
            return false;
        }

        return true;
    }

    /**
     * Imprime ordenadamente a tabela de Hash.
     */
    public void print()
    {
        print(null,null);
    }

    /**
     * Imprime ordenadamente a tabela de Hash. Os valores
     * impressos estarão compreendidos entre <b>sa</b> e
     * <b>sb</b> se estes dois parametros forem não
     * nulos.
     */
    public void print(String sa, String sb)
    {
        if ( size() < 1 ) return;

        TreeSet<String> tree= new TreeSet<String>(keySet());
        SortedSet sset= tree.tailSet((String) tree.first());
        Iterator iter= sset.iterator();

        while ( iter.hasNext() )  {
            String skey=(String) iter.next();
            if ( sa != null  &&  skey.compareTo(sa) < 0 )  continue;
            if ( sb != null  &&  skey.compareTo(sb) > 0 )  break;

            Integer I=(Integer) get(skey);
            System.out.println(I.intValue()+"\t["+skey+"]");
        }
    }


    /**
     *  -------------------------
     *  M A I N - PEQUENO EXEMPLO
     *  -------------------------
     */
    public static void main(String[] args)
    {
        //PEQUENO TEXTO ILUSTRATIVO
        String txt="EIA also reported total U.S. commercial petroleum stocks -- including crude oil, motor gasoline and heating oil -- dropped for a sixth week to 981.9 million barrels in the week ending Dec. 21. The current commercial stocks can support about one and a half months of U.S. petroleum consumption, a quite low level, said James Williams, an economist at WTRG Economics, an energy research firm. The U.S. is the world s largest crude-oil consumer, accounting for nearly a quarter of the world s production. U.S.'s Strategic Petroleum Reserves were at 695.5 million barrels, or more than one month of U.S. consumption.";

        System.out.println("\n\n---------------------------------------------------------------------");
        System.out.println("[ PEQUENO TEXTO EXEMPLO ]");
        System.out.println("---------------------------------------------------------------------");
        System.out.println(txt+"\n\n");

        //CRIAÇÃO DA HASHTABLE.
        HashStr hasht= new HashStr();
        String[] palavras= txt.split(" ");
        for (String w : palavras)  hasht.countWord(w.toLowerCase());

        //VISUALIZAR AS 10 PALAVRAS MAIS FREQUENTES.
        System.out.println("---------------------------------------------------------------------");
        System.out.println("[ AS 10 PALAVRAS MAIS FREQUENTES ]");
        System.out.println("---------------------------------------------------------------------");
        String[] vbest= hasht.rankBestN(10);
        for (int i=0; i<vbest.length; i++) {
            double p= hasht.prob(vbest[i]);
            System.out.printf(" %3d     p: %.20f   --->   [%s]\n", i, p, vbest[i]);
        }

        //VISUALIZAR A HASHTABLE
        System.out.println("\n\n---------------------------------------------------------------------");
        System.out.println("[ A HASHTABLE COMPLETA <frequência palavra> ]");
        System.out.println("---------------------------------------------------------------------");
        hasht.print();
        System.out.println("\n\n");
    }
}




